package com.gaogzhen.service.impl;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.RandomUtil;
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.CreateCache;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gaogzhen.domain.CashRecharge;
import com.gaogzhen.domain.CashRechargeAuditRecord;
import com.gaogzhen.domain.Coin;
import com.gaogzhen.domain.Config;
import com.gaogzhen.dto.AdminBankDto;
import com.gaogzhen.dto.UserDto;
import com.gaogzhen.feigh.UserServiceFeign;
import com.gaogzhen.feign.AdminBankServiceFeign;
import com.gaogzhen.mapper.CashRechargeAuditRecordMapper;
import com.gaogzhen.mapper.CashRechargeMapper;
import com.gaogzhen.mapper.CoinMapper;
import com.gaogzhen.model.CashParam;
import com.gaogzhen.model.RRException;
import com.gaogzhen.service.AccountService;
import com.gaogzhen.service.CashRechargeService;
import com.gaogzhen.service.ConfigService;
import com.gaogzhen.utils.*;
import com.gaogzhen.vo.CashTradeVo;
import com.netflix.loadbalancer.ILoadBalancer;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.supercsv.cellprocessor.ift.CellProcessor;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @description ${description}
* @author Administrator
* @date 2022-11-03 11:43
* @version 1.0
*/
@Service
public class CashRechargeServiceImpl extends ServiceImpl<CashRechargeMapper, CashRecharge> implements CashRechargeService{

    @Autowired
    private UserServiceFeign userServiceFeign;

    @Autowired
    private CashRechargeAuditRecordMapper auditRecordMapper;

    @Autowired
    private AccountService accountService;

    @Autowired
    private ConfigService configService;

    @Autowired
    private CoinMapper coinMapper;

    @Autowired
    private AdminBankServiceFeign adminBankServiceFeign;

    @Autowired
    private Snowflake snowflake;

    @CreateCache(name = "CASH_RECHARGE_LOCK", expire = 100, timeUnit = TimeUnit.SECONDS, cacheType = CacheType.BOTH)
    private Cache<String, Service> lock;

    @Override
    public Page<CashRecharge> findByPage(Page<CashRecharge> page, Long coinId, Long userId, String username, String mobile, Byte status, String numMin, String numMax, String startTime, String endTime) {
        // 按最后修改时间倒序查询
        page.addOrder(OrderItem.desc("last_update_time"));
        LambdaQueryWrapper<CashRecharge> queryWrapper = new LambdaQueryWrapper<>();
        // 1 携带用户信息：用户名或者手机号
        Map<Long, UserDto> userDtoMap = null;
        Set<Long> ids = null;
        if (ObjectUtils.isNotEmpty(userId) || StringUtils.isNotEmpty(username) || StringUtils.isNotEmpty(mobile)) {
            // 远程调用
            userDtoMap = userServiceFeign.getBriefUsers(ObjectUtils.isEmpty(userId) ? null : Collections.singletonList(userId), username, mobile);
            if (MapUtils.isEmpty(userDtoMap)) {
                return new Page<>();
            }
            ids = userDtoMap.keySet();
            queryWrapper.in(CollectionUtils.isNotEmpty(ids), CashRecharge::getUserId, ids);
        }
        // 2 没有携带
        LambdaQueryWrapper<CashRecharge> wrapper = queryWrapper.eq(ObjectUtils.isNotEmpty(coinId), CashRecharge::getCoinId, coinId)
                .eq(ObjectUtils.isNotEmpty(status), CashRecharge::getStatus, status)
                .between(
                        StringUtils.isNotEmpty(numMin) && StringUtils.isNotEmpty(numMax),
                        CashRecharge::getNum,
                        numMin == null? null : new BigDecimal(numMin), numMax == null ? null: new BigDecimal(numMax))
                .between(
                        StringUtils.isNotEmpty(startTime) && StringUtils.isNotEmpty(endTime),
                        CashRecharge::getCreated,
                        startTime, endTime + " 23:59:59");

        Page<CashRecharge> cashRechargePage = page(page, wrapper);
        List<CashRecharge> records = cashRechargePage.getRecords();

        if (CollectionUtils.isNotEmpty(records)) {
            // 远程调用
            if (MapUtils.isEmpty(userDtoMap)) {
                List<Long> idList = records.stream().map(CashRecharge::getUserId).collect(Collectors.toList());
                userDtoMap = userServiceFeign.getBriefUsers(idList,null, null);
            }
            Map<Long, UserDto> finalUserDtoMap = userDtoMap;
            records.forEach(c -> {
                UserDto userDto = finalUserDtoMap.get(c.getUserId());
                if (userDto == null) {
                    c.setUsername(Constants.UNKNOWN_USER);
                    c.setRealName(Constants.UNKNOWN_USER);
                } else {
                    c.setUsername(userDto.getUsername());
                    c.setRealName(userDto.getRealName());
                }

            });
        }

        return cashRechargePage;
    }

    @Override
    public void exportCsv(Long coinId, Long userId, String username, String mobile, Byte status, String numMin, String numMax, String startTime, String endTime) {

        LambdaQueryWrapper<CashRecharge> queryWrapper = new LambdaQueryWrapper<>();
        // 1 携带用户信息：用户名或者手机号
        Map<Long, UserDto> userDtoMap = null;
        Set<Long> ids = null;
        if (ObjectUtils.isNotEmpty(userId) || StringUtils.isNotEmpty(username) || StringUtils.isNotEmpty(mobile)) {
            // 远程调用
            userDtoMap = userServiceFeign.getBriefUsers(ObjectUtils.isEmpty(userId) ? null : Collections.singletonList(userId), username, mobile);
            if (MapUtils.isEmpty(userDtoMap)) {
                return;
            }
            ids = userDtoMap.keySet();
            queryWrapper.in(CollectionUtils.isNotEmpty(ids), CashRecharge::getUserId, ids);
        }
        // 2 没有携带
        LambdaQueryWrapper<CashRecharge> wrapper = queryWrapper.eq(ObjectUtils.isNotEmpty(coinId), CashRecharge::getCoinId, coinId)
                .eq(ObjectUtils.isNotEmpty(status), CashRecharge::getStatus, status)
                .between(
                        StringUtils.isNotEmpty(numMin) && StringUtils.isNotEmpty(numMax),
                        CashRecharge::getNum,
                        numMin == null? null : new BigDecimal(numMin), numMax == null ? null: new BigDecimal(numMax))
                .between(
                        StringUtils.isNotEmpty(startTime) && StringUtils.isNotEmpty(endTime),
                        CashRecharge::getCreated,
                        startTime, endTime + " 23:59:59");


        List<CashRecharge> records = list(wrapper);

        if (CollectionUtils.isNotEmpty(records)) {
            // 远程调用
            if (MapUtils.isEmpty(userDtoMap)) {
                List<Long> idList = records.stream().map(CashRecharge::getUserId).collect(Collectors.toList());
                userDtoMap = userServiceFeign.getBriefUsers(idList,null, null);
            }
            Map<Long, UserDto> finalUserDtoMap = userDtoMap;
            records.forEach(c -> {
                UserDto userDto = finalUserDtoMap.get(c.getUserId());
                if (userDto == null) {
                    c.setUsername(Constants.UNKNOWN_USER);
                    c.setRealName(Constants.UNKNOWN_USER);
                } else {
                    c.setUsername(userDto.getUsername());
                    c.setRealName(userDto.getRealName());
                }

            });
        }
        // 导出数据
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        CellProcessor[] processors = new CellProcessor[]{
                CellProcessorAdaptorUtils.long2StringAdaptor(), CellProcessorAdaptorUtils.long2StringAdaptor(), null, null, null,
                CellProcessorAdaptorUtils.moneyAdaptor(), CellProcessorAdaptorUtils.moneyAdaptor(), CellProcessorAdaptorUtils.moneyAdaptor(),
                CellProcessorAdaptorUtils.payTypeAdaptor(), null, null, CellProcessorAdaptorUtils.timeAdaptor(null), CellProcessorAdaptorUtils.timeAdaptor(null),
                CellProcessorAdaptorUtils.statusAdaptor(Constants.CASH_RECHARGE_STATUS), null, null

        };
        try {
            ReportCsvUtils.reportList(requestAttributes.getResponse(), Constants.CASH_RECHARGE_HEADERS, Constants.CASH_RECHARGE_PROPERTIES, "场外交易充值审核.csv",records, processors);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void cashRechargeAudit(Long currentUserId, CashRechargeAuditRecord auditRecord) {
        // 同一时刻，只允许一名员工审核,加锁
        lock.tryLockAndRun(auditRecord.getId() + "", 300, TimeUnit.SECONDS, () -> {
            Long recordId = auditRecord.getId();
            CashRecharge cashRecharge = getById(recordId);
            if (cashRecharge == null) {
                throw new RRException("现金充值记录不存在");
            }
            Byte status = cashRecharge.getStatus();
            if (status == 1) {
                throw new RRException("审核已通过");
            }
            CashRechargeAuditRecord auditRecord1 = new CashRechargeAuditRecord();
            auditRecord1.setAuditUserId(currentUserId);
            auditRecord1.setStatus(auditRecord.getStatus());
            auditRecord1.setRemark(auditRecord.getRemark());
            byte step = (byte) (cashRecharge.getStep() + 1);
            auditRecord1.setStep(step);

            int insert = auditRecordMapper.insert(auditRecord1);
            if (insert != 1) {
                throw new RRException("新增审核记录失败");
            }
            cashRecharge.setStatus(auditRecord.getStatus());
            cashRecharge.setAuditRemark(auditRecord.getRemark());
            cashRecharge.setStep(step);
            // 审核未通过
            if (auditRecord.getStatus() == 2) {
                boolean update = updateById(cashRecharge);
                if (!update) {
                    throw new RRException("更新充值记录失败");
                }
            } else {
                // 审核通过，用户账户加钱
                accountService.transferAccountAmount(currentUserId, cashRecharge.getUserId(), cashRecharge.getCoinId(), cashRecharge.getNum(), cashRecharge.getFee(), cashRecharge.getId(), auditRecord.getRemark(), "recharge_into", (byte) 1);
                boolean update = updateById(cashRecharge);
                if (!update) {
                    throw new RRException("更新充值记录失败");
                }
            }
        });
    }

    @Override
    public Page<CashRecharge> findUserCashRecharge(Long userId, Page<CashRecharge> page, Byte status) {
        page.addOrder(OrderItem.desc("last_update_time"));
        return page(page, new LambdaQueryWrapper<CashRecharge>()
                    .eq(CashRecharge::getUserId, userId)
                    .eq(status != null, CashRecharge::getStatus, status)
        );
    }

    @Override
    public CashTradeVo buy(Long userId, CashParam cashParam) {
        // 校验参数
        checkCashParam(cashParam);
        // 查询币种
        Coin coin = coinMapper.selectById(cashParam.getCoinId());
        if (coin == null) {
            throw new RRException("币种不存在");
        }
        // 查询银行卡
        List<AdminBankDto> adminBanks = adminBankServiceFeign.getAllAdminBanks();
        AdminBankDto adminBankDto = loadBalancer(adminBanks);
        // 生成订单号、参考号
        String orderNo = String.valueOf(snowflake.nextId());
        String remark = RandomUtil.randomNumbers(6);
        // 插入一条充值记录
        CashRecharge cashRecharge = new CashRecharge();
        cashRecharge.setUserId(userId);

        // 设置银行卡信息
        cashRecharge.setName(adminBankDto.getName());
        cashRecharge.setBankCard(adminBankDto.getBankCard());
        cashRecharge.setBankName(adminBankDto.getBankName());

        // 设置币种信息
        cashRecharge.setTradeno(orderNo);
        cashRecharge.setCoinId(cashParam.getCoinId());
        cashRecharge.setCoinName(coin.getName());
        cashRecharge.setNum(cashParam.getNum());
        cashRecharge.setMum(cashParam.getMum());
        cashRecharge.setFee(BigDecimal.ZERO);

        // 其他信息
        cashRecharge.setType("linepay");
        cashRecharge.setStatus((byte) 0);
        cashRecharge.setStep((byte) 1);
        cashRecharge.setRemark(remark);
        // 更新数据库
        SqlExecuteUtils.sqlExceptionHandler(this::save, cashRecharge, "插入流水记录失败");
        // 返回结果

        CashTradeVo cashTradeVo = new CashTradeVo();
        cashTradeVo.setName(adminBankDto.getName());
        cashTradeVo.setBankName(adminBankDto.getBankName());
        cashTradeVo.setBankCard(adminBankDto.getBankCard());
        cashTradeVo.setRemark(remark);
        cashTradeVo.setAmount(cashParam.getMum());
        cashTradeVo.setStatus((byte) 0);

        return cashTradeVo;
    }

    /**
     * 银行卡中随机选择一个银行卡
     * @param adminBanks    银行卡集合
     * @return              随机银行卡
     */
    private AdminBankDto loadBalancer(List<AdminBankDto> adminBanks) {
        if (CollectionUtils.isEmpty(adminBanks)) {
            throw new RRException("没有可用银行卡");
        }
        int size = adminBanks.size();
        return adminBanks.get(RandomUtil.randomInt(size));
    }

    /**
     * 校验充值参数
     * @param cashParam 充值参数
     */
    private void checkCashParam(CashParam cashParam) {
        // 校验最小充值数量
        Config withDrowConfig = configService.getByCode("RECHARGE_MIN_AMOUNT");
        BigDecimal minRecharge = new BigDecimal(withDrowConfig.getValue());
        if (minRecharge.compareTo(cashParam.getNum()) > 0) {
            throw new RRException("不能小于最小充值数量:" + minRecharge);
        }

    }

}
